/*
 * pgcrypto.c
 *		PostgreSQL 的各种加密功能。
 *
 * Copyright (c) 2001 Marko Kreen
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *	  notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *	  notice, this list of conditions and the following disclaimer in the
 *	  documentation和/或其他提供的材料与分发一起。
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * contrib/pgcrypto/pgcrypto.c
 */

#include "postgres.h"

#include <ctype.h>

#include "parser/scansup.h"
#include "pgcrypto.h"
#include "px-crypt.h"
#include "px.h"
#include "utils/builtins.h"
#include "utils/uuid.h"

PG_MODULE_MAGIC;

/* 私有内容 */

typedef int (*PFN) (const char *name, void **res);
static void *fc_find_provider(text *fc_name, PFN fc_pf, const char *fc_desc, int fc_silent);

/* SQL 函数：hash(bytea, text) 返回 bytea */
PG_FUNCTION_INFO_V1(pg_digest);

Datum pg_digest(PG_FUNCTION_ARGS)
{
	bytea	   *fc_arg;
	text	   *fc_name;
	unsigned	fc_len,
				fc_hlen;
	PX_MD	   *fc_md;
	bytea	   *fc_res;

	fc_name = PG_GETARG_TEXT_PP(1);

	/* 如果失败将给出错误 */
	fc_md = fc_find_provider(fc_name, (PFN) px_find_digest, "Digest", 0);

	fc_hlen = px_md_result_size(fc_md);

	fc_res = (text *) palloc(fc_hlen + VARHDRSZ);
	SET_VARSIZE(fc_res, fc_hlen + VARHDRSZ);

	fc_arg = PG_GETARG_BYTEA_PP(0);
	fc_len = VARSIZE_ANY_EXHDR(fc_arg);

	px_md_update(fc_md, (uint8 *) VARDATA_ANY(fc_arg), fc_len);
	px_md_finish(fc_md, (uint8 *) VARDATA(fc_res));
	px_md_free(fc_md);

	PG_FREE_IF_COPY(fc_arg, 0);
	PG_FREE_IF_COPY(fc_name, 1);

	PG_RETURN_BYTEA_P(fc_res);
}

/* SQL 函数：hmac(data:bytea, key:bytea, type:text) 返回 bytea */
PG_FUNCTION_INFO_V1(pg_hmac);

Datum pg_hmac(PG_FUNCTION_ARGS)
{
	bytea	   *fc_arg;
	bytea	   *fc_key;
	text	   *fc_name;
	unsigned	fc_len,
				fc_hlen,
				fc_klen;
	PX_HMAC    *fc_h;
	bytea	   *fc_res;

	fc_name = PG_GETARG_TEXT_PP(2);

	/* 如果失败将给出错误 */
	fc_h = fc_find_provider(fc_name, (PFN) px_find_hmac, "HMAC", 0);

	fc_hlen = px_hmac_result_size(fc_h);

	fc_res = (text *) palloc(fc_hlen + VARHDRSZ);
	SET_VARSIZE(fc_res, fc_hlen + VARHDRSZ);

	fc_arg = PG_GETARG_BYTEA_PP(0);
	fc_key = PG_GETARG_BYTEA_PP(1);
	fc_len = VARSIZE_ANY_EXHDR(fc_arg);
	fc_klen = VARSIZE_ANY_EXHDR(fc_key);

	px_hmac_init(fc_h, (uint8 *) VARDATA_ANY(fc_key), fc_klen);
	px_hmac_update(fc_h, (uint8 *) VARDATA_ANY(fc_arg), fc_len);
	px_hmac_finish(fc_h, (uint8 *) VARDATA(fc_res));
	px_hmac_free(fc_h);

	PG_FREE_IF_COPY(fc_arg, 0);
	PG_FREE_IF_COPY(fc_key, 1);
	PG_FREE_IF_COPY(fc_name, 2);

	PG_RETURN_BYTEA_P(fc_res);
}


/* SQL 函数：pg_gen_salt(text) 返回 text */
PG_FUNCTION_INFO_V1(pg_gen_salt);

Datum pg_gen_salt(PG_FUNCTION_ARGS)
{
	text	   *fc_arg0 = PG_GETARG_TEXT_PP(0);
	int			fc_len;
	char		fc_buf[PX_MAX_SALT_LEN + 1];

	text_to_cstring_buffer(fc_arg0, fc_buf, sizeof(fc_buf));
	fc_len = px_gen_salt(fc_buf, fc_buf, 0);
	if (fc_len < 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("gen_salt: %s", px_strerror(fc_len))));

	PG_FREE_IF_COPY(fc_arg0, 0);

	PG_RETURN_TEXT_P(cstring_to_text_with_len(fc_buf, fc_len));
}

/* SQL 函数：pg_gen_salt(text, int4) 返回 text */
PG_FUNCTION_INFO_V1(pg_gen_salt_rounds);

Datum pg_gen_salt_rounds(PG_FUNCTION_ARGS)
{
	text	   *fc_arg0 = PG_GETARG_TEXT_PP(0);
	int			fc_rounds = PG_GETARG_INT32(1);
	int			fc_len;
	char		fc_buf[PX_MAX_SALT_LEN + 1];

	text_to_cstring_buffer(fc_arg0, fc_buf, sizeof(fc_buf));
	fc_len = px_gen_salt(fc_buf, fc_buf, fc_rounds);
	if (fc_len < 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("gen_salt: %s", px_strerror(fc_len))));

	PG_FREE_IF_COPY(fc_arg0, 0);

	PG_RETURN_TEXT_P(cstring_to_text_with_len(fc_buf, fc_len));
}

/* SQL 函数：pg_crypt(psw:text, salt:text) 返回 text */
PG_FUNCTION_INFO_V1(pg_crypt);

Datum pg_crypt(PG_FUNCTION_ARGS)
{
	text	   *fc_arg0 = PG_GETARG_TEXT_PP(0);
	text	   *fc_arg1 = PG_GETARG_TEXT_PP(1);
	char	   *fc_buf0,
			   *fc_buf1,
			   *fc_cres,
			   *fc_resbuf;
	text	   *fc_res;

	fc_buf0 = text_to_cstring(fc_arg0);
	fc_buf1 = text_to_cstring(fc_arg1);

	fc_resbuf = palloc0(PX_MAX_CRYPT);

	fc_cres = px_crypt(fc_buf0, fc_buf1, fc_resbuf, PX_MAX_CRYPT);

	pfree(fc_buf0);
	pfree(fc_buf1);

	if (fc_cres == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION),
				 errmsg("crypt(3) returned NULL")));

	fc_res = cstring_to_text(fc_cres);

	pfree(fc_resbuf);

	PG_FREE_IF_COPY(fc_arg0, 0);
	PG_FREE_IF_COPY(fc_arg1, 1);

	PG_RETURN_TEXT_P(fc_res);
}

/* SQL 函数：pg_encrypt(bytea, bytea, text) 返回 bytea */
PG_FUNCTION_INFO_V1(pg_encrypt);

Datum pg_encrypt(PG_FUNCTION_ARGS)
{
	int			fc_err;
	bytea	   *fc_data,
			   *fc_key,
			   *fc_res;
	text	   *fc_type;
	PX_Combo   *fc_c;
	unsigned	fc_dlen,
				fc_klen,
				fc_rlen;

	fc_type = PG_GETARG_TEXT_PP(2);
	fc_c = fc_find_provider(fc_type, (PFN) px_find_combo, "Cipher", 0);

	fc_data = PG_GETARG_BYTEA_PP(0);
	fc_key = PG_GETARG_BYTEA_PP(1);
	fc_dlen = VARSIZE_ANY_EXHDR(fc_data);
	fc_klen = VARSIZE_ANY_EXHDR(fc_key);

	fc_rlen = px_combo_encrypt_len(fc_c, fc_dlen);
	fc_res = palloc(VARHDRSZ + fc_rlen);

	fc_err = px_combo_init(fc_c, (uint8 *) VARDATA_ANY(fc_key), fc_klen, NULL, 0);
	if (!fc_err)
		fc_err = px_combo_encrypt(fc_c, (uint8 *) VARDATA_ANY(fc_data), fc_dlen,
							   (uint8 *) VARDATA(fc_res), &fc_rlen);
	px_combo_free(fc_c);

	PG_FREE_IF_COPY(fc_data, 0);
	PG_FREE_IF_COPY(fc_key, 1);
	PG_FREE_IF_COPY(fc_type, 2);

	if (fc_err)
	{
		pfree(fc_res);
		ereport(ERROR,
				(errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION),
				 errmsg("encrypt error: %s", px_strerror(fc_err))));
	}

	SET_VARSIZE(fc_res, VARHDRSZ + fc_rlen);
	PG_RETURN_BYTEA_P(fc_res);
}

/* SQL 函数：pg_decrypt(bytea, bytea, text) 返回 bytea */
PG_FUNCTION_INFO_V1(pg_decrypt);

Datum pg_decrypt(PG_FUNCTION_ARGS)
{
	int			fc_err;
	bytea	   *fc_data,
			   *fc_key,
			   *fc_res;
	text	   *fc_type;
	PX_Combo   *fc_c;
	unsigned	fc_dlen,
				fc_klen,
				fc_rlen;

	fc_type = PG_GETARG_TEXT_PP(2);
	fc_c = fc_find_provider(fc_type, (PFN) px_find_combo, "Cipher", 0);

	fc_data = PG_GETARG_BYTEA_PP(0);
	fc_key = PG_GETARG_BYTEA_PP(1);
	fc_dlen = VARSIZE_ANY_EXHDR(fc_data);
	fc_klen = VARSIZE_ANY_EXHDR(fc_key);

	fc_rlen = px_combo_decrypt_len(fc_c, fc_dlen);
	fc_res = palloc(VARHDRSZ + fc_rlen);

	fc_err = px_combo_init(fc_c, (uint8 *) VARDATA_ANY(fc_key), fc_klen, NULL, 0);
	if (!fc_err)
		fc_err = px_combo_decrypt(fc_c, (uint8 *) VARDATA_ANY(fc_data), fc_dlen,
							   (uint8 *) VARDATA(fc_res), &fc_rlen);

	px_combo_free(fc_c);

	if (fc_err)
		ereport(ERROR,
				(errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION),
				 errmsg("decrypt error: %s", px_strerror(fc_err))));

	SET_VARSIZE(fc_res, VARHDRSZ + fc_rlen);

	PG_FREE_IF_COPY(fc_data, 0);
	PG_FREE_IF_COPY(fc_key, 1);
	PG_FREE_IF_COPY(fc_type, 2);

	PG_RETURN_BYTEA_P(fc_res);
}

/* SQL 函数：pg_encrypt_iv(bytea, bytea, bytea, text) 返回 bytea */
PG_FUNCTION_INFO_V1(pg_encrypt_iv);

Datum pg_encrypt_iv(PG_FUNCTION_ARGS)
{
	int			fc_err;
	bytea	   *fc_data,
			   *fc_key,
			   *fc_iv,
			   *fc_res;
	text	   *fc_type;
	PX_Combo   *fc_c;
	unsigned	fc_dlen,
				fc_klen,
				fc_ivlen,
				fc_rlen;

	fc_type = PG_GETARG_TEXT_PP(3);
	fc_c = fc_find_provider(fc_type, (PFN) px_find_combo, "Cipher", 0);

	fc_data = PG_GETARG_BYTEA_PP(0);
	fc_key = PG_GETARG_BYTEA_PP(1);
	fc_iv = PG_GETARG_BYTEA_PP(2);
	fc_dlen = VARSIZE_ANY_EXHDR(fc_data);
	fc_klen = VARSIZE_ANY_EXHDR(fc_key);
	fc_ivlen = VARSIZE_ANY_EXHDR(fc_iv);

	fc_rlen = px_combo_encrypt_len(fc_c, fc_dlen);
	fc_res = palloc(VARHDRSZ + fc_rlen);

	fc_err = px_combo_init(fc_c, (uint8 *) VARDATA_ANY(fc_key), fc_klen,
						(uint8 *) VARDATA_ANY(fc_iv), fc_ivlen);
	if (!fc_err)
		fc_err = px_combo_encrypt(fc_c, (uint8 *) VARDATA_ANY(fc_data), fc_dlen,
							   (uint8 *) VARDATA(fc_res), &fc_rlen);

	px_combo_free(fc_c);

	if (fc_err)
		ereport(ERROR,
				(errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION),
				 errmsg("encrypt_iv error: %s", px_strerror(fc_err))));

	SET_VARSIZE(fc_res, VARHDRSZ + fc_rlen);

	PG_FREE_IF_COPY(fc_data, 0);
	PG_FREE_IF_COPY(fc_key, 1);
	PG_FREE_IF_COPY(fc_iv, 2);
	PG_FREE_IF_COPY(fc_type, 3);

	PG_RETURN_BYTEA_P(fc_res);
}

/* SQL 函数：pg_decrypt_iv(bytea, bytea, bytea, text) 返回 bytea */
PG_FUNCTION_INFO_V1(pg_decrypt_iv);

Datum pg_decrypt_iv(PG_FUNCTION_ARGS)
{
	int			fc_err;
	bytea	   *fc_data,
			   *fc_key,
			   *fc_iv,
			   *fc_res;
	text	   *fc_type;
	PX_Combo   *fc_c;
	unsigned	fc_dlen,
				fc_klen,
				fc_rlen,
				fc_ivlen;

	fc_type = PG_GETARG_TEXT_PP(3);
	fc_c = fc_find_provider(fc_type, (PFN) px_find_combo, "Cipher", 0);

	fc_data = PG_GETARG_BYTEA_PP(0);
	fc_key = PG_GETARG_BYTEA_PP(1);
	fc_iv = PG_GETARG_BYTEA_PP(2);
	fc_dlen = VARSIZE_ANY_EXHDR(fc_data);
	fc_klen = VARSIZE_ANY_EXHDR(fc_key);
	fc_ivlen = VARSIZE_ANY_EXHDR(fc_iv);

	fc_rlen = px_combo_decrypt_len(fc_c, fc_dlen);
	fc_res = palloc(VARHDRSZ + fc_rlen);

	fc_err = px_combo_init(fc_c, (uint8 *) VARDATA_ANY(fc_key), fc_klen,
						(uint8 *) VARDATA_ANY(fc_iv), fc_ivlen);
	if (!fc_err)
		fc_err = px_combo_decrypt(fc_c, (uint8 *) VARDATA_ANY(fc_data), fc_dlen,
							   (uint8 *) VARDATA(fc_res), &fc_rlen);

	px_combo_free(fc_c);

	if (fc_err)
		ereport(ERROR,
				(errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION),
				 errmsg("decrypt_iv error: %s", px_strerror(fc_err))));

	SET_VARSIZE(fc_res, VARHDRSZ + fc_rlen);

	PG_FREE_IF_COPY(fc_data, 0);
	PG_FREE_IF_COPY(fc_key, 1);
	PG_FREE_IF_COPY(fc_iv, 2);
	PG_FREE_IF_COPY(fc_type, 3);

	PG_RETURN_BYTEA_P(fc_res);
}

/* SQL 函数：pg_random_bytes(int4) 返回 bytea */
PG_FUNCTION_INFO_V1(pg_random_bytes);

Datum pg_random_bytes(PG_FUNCTION_ARGS)
{
	int			fc_len = PG_GETARG_INT32(0);
	bytea	   *fc_res;

	if (fc_len < 1 || fc_len > 1024)
		ereport(ERROR,
				(errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION),
				 errmsg("Length not in range")));

	fc_res = palloc(VARHDRSZ + fc_len);
	SET_VARSIZE(fc_res, VARHDRSZ + fc_len);

	/* 生成结果 */
	if (!pg_strong_random(VARDATA(fc_res), fc_len))
		px_THROW_ERROR(PXE_NO_RANDOM);

	PG_RETURN_BYTEA_P(fc_res);
}

/* SQL 函数：gen_random_uuid() 返回 uuid */
PG_FUNCTION_INFO_V1(pg_random_uuid);

Datum pg_random_uuid(PG_FUNCTION_ARGS)
{
	/* 重定向到内置函数 */
	return gen_random_uuid(fcinfo);
}

static void * fc_find_provider(text *fc_name,
			  PFN fc_provider_lookup,
			  const char *fc_desc, int fc_silent)
{
	void	   *fc_res;
	char	   *fc_buf;
	int			fc_err;

	fc_buf = downcase_truncate_identifier(VARDATA_ANY(fc_name),
									   VARSIZE_ANY_EXHDR(fc_name),
									   false);

	fc_err = fc_provider_lookup(fc_buf, &fc_res);

	if (fc_err && !fc_silent)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("Cannot use \"%s\": %s", fc_buf, px_strerror(fc_err))));

	pfree(fc_buf);

	return fc_err ? NULL : fc_res;
}
